package com.gitee.huanminabc.jfastcache.local;

import com.gitee.huanminabc.jcommon.system.AppStop;
import com.gitee.huanminabc.jfastcache.AbstractCache;
import com.gitee.huanminabc.jfastcache.CacheObj;
import com.gitee.huanminabc.jfastcache.MapDBUtil;
import com.gitee.huanminabc.jfastcache.memory.MemoryCache;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.mapdb.DB;
import org.mapdb.HTreeMap;

import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 主要策略是将内存中的缓存在本地保存一份，当内存中的缓存被清除时，从本地文件中读取, 内存读取和mapdb读写速度相差2~5倍左右 ,而mapdb读写速度和文件相差至少100倍+  数据量越大差距越大
 * 经过测试可以达到每秒几十万数据的读写速度几乎和内存一样快
 * 1. 清理过期的缓存
 * 2. 容量快满了时，清除没有使用的缓存 也就是访问次数为0的缓存
 * 3. 容量满了时，清除最早访问的缓存,直到满足80%后停止
 * 4. 以上策略每隔5分钟执行一次
 * @param <V>
 */
@Slf4j
@Getter
public abstract class AbstractLocalCache<V> extends AbstractCache<V> {

    private final String key;
    private final MemoryCache<V> memoryCache;
    private final DB mmp;
    private final HTreeMap<String, CacheObj<String, V>> localCache;
    private final LinkedBlockingQueue<Runnable> queueRun;


    protected AbstractLocalCache(String key, int capacity) {
        this(key, capacity, -1);
    }

    protected AbstractLocalCache(String key, int capacity, int timeout) {
        super(capacity, timeout);
        this.memoryCache = new MemoryCache<>(capacity, timeout);
        //将内存缓存的map赋值给memoryCache
        this.memoryCache.setCacheMap(this.cacheMap);
        this.key = key;
        this.queueRun = new LinkedBlockingQueue<>();
        mmp = MapDBUtil.defaultCreate(key);
        //每次启动都是新的HashMap, 删除创建时间超过7天的缓存项
        localCache = MapDBUtil.createHTreeMap(mmp, key);
        //添加关闭钩子
        AppStop.addShutdownHook(mmp::close);
        //持续监听队列然后执行,这样避免了频繁的IO操作导致的性能问题,虽然可能会导致延迟
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    //单线程跑是要比多线程快
                    Runnable poll = queueRun.take();
                    poll.run();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    log.warn("队列处理线程被中断", e);
                    break; // 中断后退出循环
                }
            }
        });
        thread.setDaemon(true);
        thread.start();

        //启动后延迟执行清理，避免阻塞初始化
        addQueueRun(this::prune);

        //将最近3天内访问过的缓存放入内存,使用迭代器避免一次性加载所有keys,限制加载数量
        addQueueRun(()->{
            long threeDaysAgo = System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 3;
            int maxLoadCount = Math.min(capacity, 10000); // 最多加载10000个或容量大小，避免内存占用过大
            int loadedCount = 0;
            int batchSize = 1000; // 每批处理1000个
            int count = 0;
            
            //使用迭代器遍历，避免一次性加载所有keys到内存
            Iterator<Map.Entry<String, CacheObj<String, V>>> iterator = localCache.entrySet().iterator();
            while (iterator.hasNext() && loadedCount < maxLoadCount) {
                Map.Entry<String, CacheObj<String, V>> entry = iterator.next();
                CacheObj<String, V> cacheObj = entry.getValue();
                if (cacheObj != null && cacheObj.getLastAccess() > threeDaysAgo) {
                    memoryCache.putInside(cacheObj);
                    loadedCount++;
                }
                count++;
                // 每处理一批，让出CPU，避免长时间占用队列线程
                if (count % batchSize == 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        });
    }


    //20万数据1秒左右
    @Override
    public void pruneCache() {
        //先清理内存
        this.memoryCache.pruneCache();
        //再清理本地
        prune();
    }

    @Override
    protected void prune() {
        //拿到所有的key和value
        Iterator<Map.Entry<String, CacheObj<String, V>>> iterator = localCache.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheObj<String, V>> entry = iterator.next();
            CacheObj<String, V> stringVCacheObj = entry.getValue();
            //1.gc回收   清理value为null的 ,一但value为null，说明已经被gc回收了
            if (stringVCacheObj == null) {
                iterator.remove();
                continue;
            }
            //跳过小于0的
            if (stringVCacheObj.getTtl() < 0) {
                continue;
            }
            //2.先清理缓存过期的
            if (stringVCacheObj.isExpired()) {
                iterator.remove();
                continue;
            }
            //3.如果内存快满了，清理没有使用的缓存
            if (isNearlyFull()) {
                //删除找到访问次数为0的
                if (stringVCacheObj.accessCount.get() == 0) {
                    iterator.remove();
                }
            }
        }

        //4.如果内存满了或超过120%,按照访问时间来排序，删除最早访问的,直到满足80%
        // 当内存缓存满时，从本地缓存中删除一些不常用的数据，避免它们被加载到内存
        int currentSize = cacheMap.size();
        int capacity120 = getCapacity120();
        if (currentSize >= capacity || currentSize > capacity120) {
            //使用优先级队列，只保留需要删除的数量，避免全排序 O(n log n) -> O(n log k)
            // 删除数量基于内存容量，而不是本地缓存大小
            int targetSize = Math.max(1, (int)(capacity * 0.80)); // 至少保留1个，避免容量很小时全部清理
            int localCacheSize = localCache.size();
            int needDeleteCount = Math.max(0, localCacheSize - targetSize);
            if (needDeleteCount > 0) {
                PriorityQueue<CacheObj<String, V>> priorityQueue = 
                    new PriorityQueue<>(needDeleteCount + 1, (o1, o2) -> 
                        Long.compare(o1.getLastAccess(), o2.getLastAccess()));
                
                //遍历所有条目，维护一个大小为 needDeleteCount 的堆
                // 使用迭代器而不是getValues()，避免一次性加载所有值到内存
                for (Map.Entry<String, CacheObj<String, V>> entry : localCache.entrySet()) {
                    CacheObj<String, V> cacheObj = entry.getValue();
                    if (cacheObj == null || cacheObj.getTtl() < 0) {
                        continue;
                    }
                    if (priorityQueue.size() < needDeleteCount) {
                        priorityQueue.offer(cacheObj);
                    } else {
                        CacheObj<String, V> top = priorityQueue.peek();
                        if (top != null && cacheObj.getLastAccess() < top.getLastAccess()) {
                            priorityQueue.poll();
                            priorityQueue.offer(cacheObj);
                        }
                    }
                }
                
                //删除最早访问的，只从本地缓存删除（内存缓存由内存缓存的 prune 管理）
                // 缓存targetSize避免重复计算
                int targetSize80 = (int)(capacity * 0.80);
                while (!priorityQueue.isEmpty()) {
                    CacheObj<String, V> cacheObj = priorityQueue.poll();
                    localCache.remove(cacheObj.getKey());
                    // 如果内存缓存已经降到80%以下，停止删除
                    // 直接检查size而不是调用isHalfFull()，避免重复计算
                    if (cacheMap.size() <= targetSize80) {
                        break;
                    }
                }
            }
        }
        //持久化到本地, 这个是耗时很大的操作, 会将缓存数据全部刷到磁盘
        mmp.commit();
    }


    @Override
    public CacheObj<String, V> putInside(String key, V value) {
        //先放入内存
        CacheObj<String, V> cacheObj = this.memoryCache.putInside(key, value);
        addQueueRun(() -> {
            //再放入本地
            localCache.put(key, cacheObj);
        });
        return cacheObj;
    }

    @SneakyThrows
    public void addQueueRun(Runnable runnable) {
        queueRun.put(runnable);
    }

    @Override
    public CacheObj<String, V> putInside(String key, V value, long timeout) {
        //先放入内存
        CacheObj<String, V> cacheObj = this.memoryCache.putInside(key, value, timeout);
        addQueueRun(() -> {
            //再放入本地
            localCache.put(key, cacheObj);
        });
        return cacheObj;
    }

    @Override

    public V getInside(String key) {
        //先从内存中获取
        V value = this.memoryCache.getInside(key);
        if (value != null) {
            return value;
        }
        //再从本地获取
        CacheObj<String, V> cacheObj = localCache.get(key);
        if (cacheObj == null) {
            return null;
        }
        //判断是否过期
        if (cacheObj.isExpired()){
            localCache.remove(key);
            return null;
        }
        //更新访问统计信息
        cacheObj.lastAccess = System.currentTimeMillis();
        cacheObj.accessCount.incrementAndGet();
        //同步更新后的统计信息到本地缓存
        addQueueRun(() -> {
            localCache.put(key, cacheObj);
        });
        //添加到内存
        this.memoryCache.putInside(cacheObj);
        return cacheObj.getObj();
    }

    @Override
    public V removeInside(String key) {
        //先从内存中删除
        V value = this.memoryCache.removeInside(key);
        addQueueRun(() -> {
            //再从本地删除
            localCache.remove(key);
            //防止查询的时候又把缓存放入内存的情况
            this.memoryCache.removeInside(key);
        });
        return value;
    }

    @Override
    public void clearInside() {
        //先清理本地
        localCache.clear();
        mmp.commit();//提交修改
        //在清理内存
        this.memoryCache.clearInside();
    }


    //判断队列是否空
    public boolean isQueueEmpty() {
        return queueRun.isEmpty();
    }

    //等待队列为空
    public void waitQueueEmpty() {
        while (!isQueueEmpty()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 恢复中断状态
                log.warn("等待队列为空时被中断", e);
                break; // 中断后退出循环
            }
        }
    }

    /**
     * 关闭数据库连接，释放文件锁
     */
    public void close() {
        if (mmp != null && !mmp.isClosed()) {
            try {
                // 等待队列处理完成
                waitQueueEmpty();
                // 提交最后的修改
                mmp.commit();
                // 关闭数据库
                mmp.close();
            } catch (Exception e) {
                log.error("关闭数据库时出错", e);
            }
        }
    }

}
